﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using CsGL.OpenGL;

namespace CHIP8Emulator {
    public partial class CHIP8EmulatorControl : OpenGLControl {
        private State _state = State.Empty;
        private CHIP8Device _device;
        public event ChangeStateEventHandler OnStateChanged;

        public CHIP8EmulatorControl() {
            InitializeComponent();
            OnStateChanged += (sender, e) => { };
        }

        private readonly Dictionary<Keys, byte> _keyCodes = new Dictionary<Keys, byte>
                                                                {
                                                                    {Keys.Decimal, 0x0},
                                                                    {Keys.NumPad7, 0x1},
                                                                    {Keys.NumPad8, 0x2},
                                                                    {Keys.NumPad9, 0x3},
                                                                    {Keys.NumPad4, 0x4},
                                                                    {Keys.NumPad5, 0x5},
                                                                    {Keys.NumPad6, 0x6},
                                                                    {Keys.NumPad1, 0x7},
                                                                    {Keys.NumPad2, 0x8},
                                                                    {Keys.NumPad3, 0x9},
                                                                    {Keys.NumPad0, 0xA},
                                                                    {Keys.Enter, 0xB},
                                                                    {Keys.Divide, 0xC},
                                                                    {Keys.Multiply, 0xD},
                                                                    {Keys.Subtract, 0xE},
                                                                    {Keys.Add, 0xF},
                                                                };

        private readonly uint[] _textures = new uint[1];

        public override void glDraw() {
            GL.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
            GL.glBindTexture(GL.GL_TEXTURE_2D, _textures[0]);
            if (_device != null)
                GL.glTexImage2D(GL.GL_TEXTURE_2D, 0, (int) GL.GL_RGBA8, CHIP8Device.X_RESOLUTION, CHIP8Device.Y_RESOLUTION, 0, GL.GL_RGBA, GL.GL_UNSIGNED_BYTE, _device.D);
            GL.glBegin(GL.GL_QUADS);
            GL.glTexCoord2f(0, 0);
            GL.glVertex2f(-1f, 1f);
            GL.glTexCoord2f(1, 0);
            GL.glVertex2f(1f, 1f);
            GL.glTexCoord2f(1, 1);
            GL.glVertex2f(1f, -1f);
            GL.glTexCoord2f(0, 1);
            GL.glVertex2f(-1f, -1f);
            GL.glEnd();
            SwapBuffer();
        }

        protected override void InitGLContext() {
            GL.glClearColor(0.0f, 0.0f, 0.0f, 0.0f);
            GL.glEnable(GL.GL_TEXTURE_2D);
            GL.glGenTextures(1, _textures);
            GL.glBindTexture(GL.GL_TEXTURE_2D, _textures[0]);
            GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_NEAREST);
            GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MAG_FILTER, GL.GL_NEAREST);
            GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_CLAMP);
            GL.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_CLAMP);
        }

        public void LoadFrom(string fileName) {
            _device.Init(CHIP8Device.PC_INIT_VALUE);
            using (BinaryReader reader = new BinaryReader(new FileStream(fileName, FileMode.Open, FileAccess.Read))) {
                reader.Read(_device.M, CHIP8Device.PC_INIT_VALUE, (int) new FileInfo(fileName).Length);
                reader.Close();
            }
            State = State.Loaded;
        }

        public State State {
            get { return _state; }
            set {
                if (_state != value)
                    OnStateChanged(this, new ChangeStateEventArgs(value));
                _state = value; 
            }
        }

        public void Run() {
            if (State == State.Loaded || State == State.Paused) {
                State = State.Started;
                _device.Start();
            }
            else
                throw new Exception("Invalid state");
        }

        public void Pause() {
            if (State == State.Started) {
                State = State.Paused;
                _device.Pause();
            }
            else
                throw new Exception("Invalid state");
        }

        public void Stop() {
            if (State == State.Started || State == State.Paused) {
                State = State.Loaded;
                _device.Stop();
            }
            else
                throw new Exception("Invalid state");
        }

        public void InitBy(Logger logger) {
            _device = new CHIP8Device(logger);
            _device.OnCompleted += (sender, e) => { State = State.Loaded; };
        }

        public void PressKey(Keys keyCode) {
            if (_keyCodes.ContainsKey(keyCode))
                _device.PressKey(_keyCodes[keyCode]);
        }

        public void ReleaseKey(Keys keyCode) {
            if (_keyCodes.ContainsKey(keyCode))
                _device.ReleaseKey(_keyCodes[keyCode]);
        }

        private void timer1_Tick(object sender, EventArgs e) {
            glDraw();
            _device.TickTimers();
        }
    }

    public delegate void ChangeStateEventHandler(object sender, ChangeStateEventArgs e);

    public class ChangeStateEventArgs {
        private readonly State _state;

        public ChangeStateEventArgs(State state) {
            _state = state;
        }

        public State State {
            get { return _state; }
        }
    }

    public enum State {
        Empty,
        Loaded,
        Started,
        Paused,
    }
}